﻿using AutoMapper;
using Max.PH5_2006.ShoppingSite.Domain;
using Max.PH5_2006.ShoppingSite.Domain.Shared;
using Max.PH5_2006.ShoppingSite.DTO;
using Max.PH5_2006.ShoppingSite.IRespository;
using Max.PH5_2006.ShoppingSite.IService;
using Max.PH5_2006.ShoppingSite.Respository;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Max.PH5_2006.ShoppingSite.Service
{
    public class OrderService : BaseService<OrderEntity, OrderEntity, OrderEntity>, IOrderService
    {
        IOrderRespository _orderRespository;
        IOrderDetailRespository _orderDetailRespository;
        IConfiguration _configuration;
        IGoodsRespository _goodsRespository;
        ILogRespository _log;
        public OrderService(IOrderRespository respository,
                IMapper map,
                IOrderDetailRespository orderDetailRespository,
                IGoodsRespository goodsRespository,
                IConfiguration configuration,
                ILogRespository log) : base(respository, map)
        {
            _orderRespository = respository;
            _orderDetailRespository = orderDetailRespository;
            _goodsRespository = goodsRespository;
            _configuration = configuration;
            _log = log;
        }

        #region 添加订单


        //添加订单
        public Tuple<bool, string,string> OrderAdd(OrderEntity order, List<OrderDetailEntity> orderDetail)
        {
            string msg = "";
            try
            {
                /*
             *  后台保存订单要做这三件事：
             *  1、保存订单信息（订单有状态等）
             *  2、已经在订单里的商品，要从购物车中移走
             *  3、商品库存需要处理（可用库存）
             * **/

                /*
                 * 一、需要保存的订单信息，具体包含哪些信息
                    1）商品信息 ---有可能会有多个商品
                    2）用户收件人信息 
                    3）订单的基本信息（金额、发票、配送时间、支付方式。。。）

                    订单基本信息：需要生成订单号
                    //在练习的时候，订单号用雪花、guid、时间戳、随机数都可以
                    //在实际项目业务中，订单号是有一定规则性的。
                 * **/

                #region 订单的基本信息和收件人信息（通过前台传进来，就不用一个一个属性去赋值了）
                //OrderEntity order = new();  //订单的基本信息和收件人信息            
                //order.OrderTotal = 0; //订单总金额
                //order.OrderStatue = Domain.Shared.OrderStatueEnum.待付款;
                //order.OrderNo = ""; //订单号（需要自动生成）
                //order.PayStatue = Domain.Shared.PayStatusEnum.未付款;
                //order.PayTotal = 0;//支付金额（支付金额和订单总金额，不一定是一样的。比如满100减20，那么订单金额是100，实际支付80）
                //order.Freight = 0;//运费
                //order.MemberId = Guid.NewGuid();//会员Id 
                //order.Rate = 0;//优惠金额
                //order.OrderTime = System.DateTime.Now; //下单日期
                //order.RecivedName = "";//收件人姓名
                //order.RecivedPhone = "";//收件人电话
                //order.RecivedAddress = "";//收件人地址
                #endregion

                order.OrderNo = CreateOrderNo();
                order.OrderStatue = Domain.Shared.OrderStatueEnum.待付款;
                order.PayStatue = Domain.Shared.PayStatusEnum.未付款;

                _orderRespository.Add(order);

                #region 订单对应的多个商品信息
                //List<OrderDetailEntity> orderDetail = new();  //订单对应的多个商品信息

                //OrderDetailEntity detail = new OrderDetailEntity
                //{
                //    GoodsCount = 0, //购买的商品数量
                //    GoodsId = 0, //购买的商品id
                //    GoodsPrice = 0,//购买的商品的价格
                //    OrderNo = order.OrderNo,//这一个订单的订单号                  
                //};
                //orderDetail.Add(detail);
                #endregion

                List<CarEntityDto> goodsIdList = new List<CarEntityDto>();
                orderDetail.ForEach(x =>
                {
                    x.OrderNo = order.OrderNo;
                    _orderDetailRespository.Add(x);


                    //要从购物车中删除的商品。
                    CarEntityDto car = new()
                    {
                        GoodsPrice = x.GoodsPrice,
                        GoodsNumber = x.GoodsCount,
                        GoodsName = x.GoodsName,
                        GoodsId = x.GoodsId.ToString()
                    };
                    goodsIdList.Add(car);
                });


                /*
                 * 二、从购物中移车商品
                 * **/
                var d = RemoveGoodsFromCar(order.MemberId.ToString(), goodsIdList);
                msg += $"操作购物车：{d.Item1},{d.Item2}";
                

                /*
                 * 三、处理库存
                 * **/
                var h = HandleStock(goodsIdList);
                msg += $"|操作库存：{h.Item1},{h.Item2}";

                //发送短信......
                //发送邮件......

                return Tuple.Create(true, msg,order.OrderNo);
            }
            catch (Exception ex)
            {
                return Tuple.Create(false, ex.Message,"");
            }

        }


        /*
         * 从购物车中，移去已经在订单里的商品
         * 1、得知道是谁的购物车   --- 在redis中，是通过memberid来唯一识别会员的
         * 2、得知道删除购物车中的哪些商品  --- 在redis中，根据商品id去找到，并删除
         * 
         * **/
        private (bool, string) RemoveGoodsFromCar(string memberId, List<CarEntityDto> goodsIdList)
        {
            try
            {
                //得获取这个会员的具体购物车
                string key = memberId.ToUpper();

                //这个redis服务器的连接字符串，在别的地方（我们操作购物车的时候）也有用到，
                //所以：为了以后更改redis服务器方便，我将连接字符串放到配置文件中（appsetting.json）

                //new CSRedis.CSRedisClient("127.0.0.1:6379");
                string host = _configuration.GetSection("RedisServer:Host").Value;
                string port = _configuration.GetSection("RedisServer:Port").Value;
                var redis = new CSRedis.CSRedisClient($"{host}:{port}");

                //判断redis服务器中，有没有这个key
                if (!redis.Exists(key))
                {
                    return (false, "用户key不存在！");
                }

                //如果存在这个key，那么就获取这个key的数据（我们存入redis的是什么数据类型格式，取出来就用什么样的数据格式接收转换）
                //var data = redis.Get(key);
                var data = redis.Get<List<CarEntityDto>>(key);
                if (data == null || data.Count <= 0)
                {
                    return (false, "购物车中没有商品数据");
                }

                goodsIdList.ForEach(x =>
                {
                    //购物车中有商品，则去找到具体要删除的商品
                    //where获得的数据是集合，first是一定要有数据,firstordefault可以为null
                    var m = data.Where(p => p.GoodsId.Equals(x.GoodsId)).FirstOrDefault();

                    if (m != null)
                    {
                        data.Remove(m);
                    }
                });
                redis.Set(key, data); //删除操作完成后，需要更新缓存

                return (true, "移除成功！");
            }
            catch (Exception ex)
            {
                return (false, ex.Message);
            }
            
        }


        /*
         * 订单生成成功，则需要处理可用库存(将订单中的商品数量从可用库存库减去)
         * 1、得知道订单号的有哪些商品
         * 2、是在所有的商品数据中，进行库存处理，所以需要先拿到所有的商品
         * **/
        private (bool, string) HandleStock(List<CarEntityDto> goodsIdList)
        {
            try
            {
                var goods = _goodsRespository.Query();
                goodsIdList.ForEach(x =>
                {
                    //找到要修改库存的哪个商品(根据订单商品的商品id)
                    var m = goods.Where(p => x.GoodsId.Equals(p.Id.ToString())).FirstOrDefault();
                    if (m != null)
                    {
                        //可用库存、锁定库存（订单未付款，需要锁定购买的数量）
                        m.LockStock += (int)x.GoodsNumber;
                        m.UsableStock -= (int)x.GoodsNumber;

                        //将更的数据（内存中）同步到数据库
                        _goodsRespository.Update(m);
                    }
                });
                return (true, "修改库存成功");
            }
            catch (Exception ex)
            {

                return (false, ex.Message);
            }
            
        }

        /*        
         * 1、用雪花ID作为订单号
         * 2、自定义订单生成规则     
         * 
         * **/
        //生成订单号（订单号是不允许被重复的）
        private string CreateOrderNo()
        {
            //雪花算法ID
            //var iworker =  new Snowflake.Core.IdWorker(1, 1);
            //return iworker.NextId().ToString();

            //自定义订单号规则（12-18位）
            /*
             * 订单号：P202212279583
             * 解析：P + 20221227 + 9583
             * 意义：
             *      P  表示是这一笔订单来自于PC端。区分来源
             *      20221227  表示当天的日期
             *      9583  表示当天的流水号。每天的0点业务流水号归0  （4位数极限是9999，表示当天下单的人数，不会超过这个数）
             * **/
            string type = "P";
            string time = System.DateTime.Now.ToString("yyyyMMdd");
            string no = new Random().Next(0, 9999).ToString("0000");  //不足4位，在左边自动补0  //示例中用随机数替代流水号，有可能会重复

            return $"{type}{time}{no}";
        }

        #endregion

        #region 订单付款

        public Tuple<bool,string> OrderPay(string orderNo,PayTypeEnum payType)
        {
            try
            {
                /*
             * 立即支付按钮，需要做三件事
             * 1、调用支付接口进行支付
             * 2、修改订单状态
             * 3、修改商品库存
             * 
             * **/


                //1、调用支付接口进行支付
                #region 伪代码

                /*
                if ("支付方式" == "微信支付")
                {
                    //调用微信支付接口
                }
                else if ("支付方式" == "支付宝支付")
                {
                    //调用支付宝支付接口
                }
                else if ("支付方式" == "银联支付")
                {
                    //调用银联支付接口
                }*/

                #endregion

                switch (payType)
                {
                    case PayTypeEnum.微信:
                        //调用微信支付接口
                        //WeiXinPay weixin = new WeiXinPay();
                        //weixin.Pay();
                        break;
                    case PayTypeEnum.银行卡:
                        //调用银行卡支付接口
                        //BankPay bank = new BankPay();
                        //bank.Pay();
                        break;
                    default:
                        //调用支付宝支付接口
                        //AlipayPay alipay = new AlipayPay();
                        //alipay.Pay();
                        break;
                }

                //2、修改订单状态（我们根据什么条件来修改订单的状态呢？ --- 我们只知道订单号）
                //所以需要根据订单号，先去找到这条订单数据（该订单除了商品之外的所有信息--订单主表）。
                var order = _orderRespository.Query().Where(x => x.OrderNo.Equals(orderNo)).FirstOrDefault();
                //再去修改这个订单的状态
                order.OrderStatue = OrderStatueEnum.待发货; //表示已付款
                order.PayStatue = PayStatusEnum.已付款; //支付状态
                order.PayType = payType;  //支付类型
                                          //实体中，少了一个付款时间
                                          //order.PayDate = System.DateTime.Now;
                order.UpdatedBy = order.MemberId.ToString();//"system";
                order.UpdatedTime = System.DateTime.Now;

                _orderRespository.Update(order);

                //===========如果需要写入日志，则可以在这拼接写入=============
                _log.Add(new LogEntity
                {
                    app_name = typeof(OrderService).ToString(),
                    message = $"会员：{order.MemberId}，支付了订单（订单号：{orderNo}），总金额为：{order.PayTotal}"
                });

                //3、修改商品库存（怎么知道要修改哪些商品的库存？）
                //已知条件只有订单号，所以我们得通过订单号去获取这一笔订单的所有商品
                //然后再去修改这些商品的锁定库存（在所有商品中，找到这个要修改的订单商品）
                var orderGoods = _orderDetailRespository.Query().Where(x => x.OrderNo.Equals(orderNo)).ToList();

                //所有的商品
                var goods = _goodsRespository.Query().ToList();

                //循环订单商品，然后一个一个地去找，找到后，进行修改
                orderGoods.ForEach(x =>
                {
                    //根据ID找到具体的商品
                    var m = goods.Where(p => p.Id == x.GoodsId).FirstOrDefault();
                    if (m != null)
                    {
                        m.LockStock -= x.GoodsCount; //将锁定库存取消
                        _goodsRespository.Update(m);
                    }
                });

                return Tuple.Create(true, "付款成功");
            }
            catch (Exception ex)
            {
                return Tuple.Create(false, ex.Message);
            }
        }

        #endregion
    }


    public class OrderDetailService : BaseService<OrderDetailEntity, OrderDetailEntity, OrderDetailEntity>, IOrderDetailService
    {
        public OrderDetailService(IOrderDetailRespository respository, IMapper map) : base(respository, map)
        {
        }
    }
}
